package hc

import (
	"fmt"
	"net"
	"strconv"
	"time"

	"github.com/hashicorp/consul/api"
)

// Register注册相关配置变量
var (
	RegisterAddress = ":8500" // RegisterAddress 注册中心地址
	Weight          = 1       // 权重
)

func registerServer(protocol, address string, port int) (err error) {
	config := api.DefaultConfig()
	config.Address = RegisterAddress // 注册中心地址
	client, err := api.NewClient(config)
	if err != nil {
		return fmt.Errorf("注册中心连接失败 : %w", err)
	}
	serviceAddress := fmt.Sprintf("%s:%d", address, port)
	meta := map[string]string{"weight": strconv.Itoa(Weight), "protocol": protocol}
	registration := &api.AgentServiceRegistration{
		ID:      serviceAddress, // 服务节点的名称
		Name:    ServiceName,    // 服务名称
		Port:    port,           // 服务端口
		Meta:    meta,           // 附带信息
		Address: address,        // 服务 IP
	}
	registration.Check = &api.AgentServiceCheck{ // 健康检查
		TCP:                            serviceAddress,
		Timeout:                        "3s",
		Interval:                       "5s",  // 健康检查间隔
		DeregisterCriticalServiceAfter: "30s", // check失败后15秒删除本服务，注销时间，相当于过期时间
		// HTTP:                           fmt.Sprintf("http://%s:%d%s", registration.Address, checkPort, "/check"),
		// GRPC:     fmt.Sprintf("%v:%v/%v", IP, r.Port, r.Service),// grpc 支持，执行健康检查的地址，service 会传到 Health.Check 函数中
	}

	err = client.Agent().ServiceRegister(registration)
	if err != nil {
		return
	}
	var lastIndex uint64 = 0
	serviceStore = make(map[string][]serviceInfo)
	balancerStore = make(map[string]*balancer)
	newLastIndex := discoverServices(client, lastIndex)
	go discoverLoop(client, newLastIndex)
	return
}

// Disover 发现服务
// address 注册中心的地址
func Disover(address string) (err error) {
	config := api.DefaultConfig()
	config.Address = address // 注册中心地址
	client, err := api.NewClient(config)
	if err != nil {
		return fmt.Errorf("注册中心连接失败 : %w", err)
	}
	var lastIndex uint64 = 0
	serviceStore = make(map[string][]serviceInfo)
	balancerStore = make(map[string]*balancer)
	newLastIndex := discoverServices(client, lastIndex)
	go discoverLoop(client, newLastIndex)
	return
}

// func localIP() string {
// 	addrs, err := net.InterfaceAddrs()
// 	if err != nil {
// 		return ""
// 	}
// 	for _, address := range addrs {
// 		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
// 			if ipnet.IP.To4() != nil {
// 				return ipnet.IP.String()
// 			}
// 		}
// 	}
// 	return ""
// }

// serviceInfo 服务
type serviceInfo struct {
	ServiceID string
	Address   string
	Weight    int
}

// balancer 负载均衡
type balancer struct {
	serverWeights map[int]int // host and weight
	maxWeight     int         // 最大的权重值
	maxGCD        int         // 最大公约数
	lenOfSW       int         // 服务器数量
	i             int         // 表示上一次选择的服务器, 初始值-1
	cw            int         // 表示当前调度的权值, 初始值0
}

// distribute 调度
func (bla *balancer) distribute() int {
	for {
		bla.i = (bla.i + 1) % bla.lenOfSW

		if bla.i == 0 {
			bla.cw = bla.cw - bla.maxGCD
			if bla.cw <= 0 {
				bla.cw = bla.maxWeight
				if bla.cw == 0 {
					return 0
				}
			}
		}

		if bla.serverWeights[bla.i] >= bla.cw {
			return bla.i
		}
	}
}

// 服务列表和负载均衡对象列表 serviceStore的key和balancerStore的key是一一对应的
var serviceStore map[string][]serviceInfo
var balancerStore map[string]*balancer

// distributer 负载均衡调度器
// var distributer map[string]*balancer

// discoverLoop 监听服务变更
func discoverLoop(client *api.Client, lastIndex uint64) {
	ticket := time.NewTicker(5 * time.Second)
	for range ticket.C {
		lastIndex = discoverServices(client, lastIndex)
	}
}

func discoverServices(client *api.Client, lastIndex uint64) uint64 {
	services, meta, err := client.Catalog().Services(&api.QueryOptions{
		// WaitIndex: lastIndex, // 一直阻塞，直到超时或有新的更新,超时时间默认5分钟，可以通过下面的WaitTime覆盖
		// WaitTime:  time.Second * 15,
	})
	if err != nil {
		return lastIndex
	}
	lastIndex = meta.LastIndex
	healthyOnly := true
	servicesMap := make(map[string][]serviceInfo)
	balancerMap := make(map[string]*balancer)
	for name := range services {
		if name == "consul" {
			continue
		}
		servicesData, _, err := client.Health().Service(name, "", healthyOnly, &api.QueryOptions{})
		if err != nil {
			continue
		}
		var serviceSlice []serviceInfo
		for _, entry := range servicesData {
			// for _, health := range entry.Checks {
			// 	if name != health.ServiceName {
			// 		continue
			// 	}
			// 	if health.Status!= "passing"{
			// 		continue
			// 	}
			// }
			weightStr := entry.Service.Meta["weight"]
			weight := 1
			if weightStr != "" {
				weight, _ = strconv.Atoi(weightStr)
			}
			// fmt.Println("  health weight:", weight, " id:", entry.Service.ID, " serviceName:", name, " address:", entry.Service.Address, " port:", entry.Service.Port)
			node := serviceInfo{
				ServiceID: entry.Node.ID,
				Address:   net.JoinHostPort(entry.Service.Address, strconv.Itoa(entry.Service.Port)),
				Weight:    weight,
			}
			serviceSlice = append(serviceSlice, node)
		}
		// 只要有变更，所有服务信息全部覆盖
		if len(serviceSlice) > 0 {
			servicesMap[name] = serviceSlice
			b := computeBalancer(serviceSlice)
			if b != nil {
				balancerMap[name] = b
			} else {
				delete(servicesMap, name)
			}
		}
	}
	serviceStore = servicesMap
	balancerStore = balancerMap
	return lastIndex
}

// computeBalancer 根据服务配置初始化调度器
func computeBalancer(allServers []serviceInfo) *balancer {
	var servers []serviceInfo
	servers = append(servers, allServers...)
	if len(servers) == 0 {
		return nil
	}
	bla := &balancer{
		serverWeights: make(map[int]int),
		maxWeight:     0,
		maxGCD:        1,
		lenOfSW:       len(servers),
		i:             -1,
		cw:            0,
	}

	tmpGcd := make([]int, 0, bla.lenOfSW)

	for idx, psc := range servers {
		if psc.Weight == 0 {
			psc.Weight = 1
		}
		bla.serverWeights[idx] = psc.Weight
		if psc.Weight > bla.maxWeight {
			bla.maxWeight = psc.Weight
		}
		tmpGcd = append(tmpGcd, psc.Weight)
	}

	// 求最大公约数
	bla.maxGCD = nGCD(tmpGcd, bla.lenOfSW)
	return bla
}

// gCD 求最大公约数
func gCD(a, b int) int {
	if a < b {
		a, b = b, a // swap a & b
	}

	if b == 0 {
		return a
	}

	return gCD(b, a%b)
}

// nGCD N个数的最大公约数
func nGCD(data []int, n int) int {
	if n == 1 {
		return data[0]
	}
	return gCD(data[n-1], nGCD(data, n-1))
}
